﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/* Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EffiProz.Core.Lib;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Indexes;
using EffiProz.Core.Errors;
using EffiProz.Core.Persist;
using EffiProz.Core.Store;

namespace EffiProz.Core
{
    public class FunctionCustom : FunctionSQL
    {
        public static String[] openGroupNumericFunctions = {
        "ABS", "ACOS", "ASIN", "ATAN", "ATAN2", "BITAND", "BITOR", "BITXOR",
        "CEILING", "COS", "COT", "DEGREES", "EXP", "FLOOR", "LOG", "LOG10",
        "MOD", "PI", "POWER", "RADIANS", "RAND", "ROUND", "ROUNDMAGIC", "SIGN",
        "SIN", "SQRT", "TAN", "TRUNCATE"
    };
        public static String[] openGroupStringFunctions = {
        "ASCII", "CHAR", "CONCAT", "DIFFERENCE", "HEXTORAW", "INSERT", "LCASE",
        "LEFT", "LENGTH", "LOCATE", "LTRIM", "RAWTOHEX", "REPEAT", "REPLACE",
        "RIGHT", "RTRIM", "SOUNDEX", "SPACE", "SUBSTR", "UCASE",
    };
        public static String[] openGroupDateTimeFunctions = {
        "CURDATE", "CURTIME", "DATEDIFF", "DAYNAME", "DAYOFMONTH", "DAYOFWEEK",
        "DAYOFYEAR", "HOUR", "MINUTE", "MONTH", "MONTHNAME", "NOW", "QUARTER",
        "SECOND", "SECONDS_SINCE_MIDNIGHT", "TIMESTAMPADD", "TIMESTAMPDIFF",
        "TO_CHAR", "WEEK", "YEAR"
    };
        public static String[] openGroupSystemFunctions = {
        "DATABASE", "IFNULL", "USER"
    };

        //
        private const int FUNC_ISAUTOCOMMIT = 71;
        private const int FUNC_ISREADONLYSESSION = 72;
        private const int FUNC_ISREADONLYDATABASE = 73;
        private const int FUNC_ISREADONLYDATABASEFILES = 74;
        private const int FUNC_DATABASE = 75;
        private const int FUNC_IDENTITY = 76;
        private const int FUNC_SYSDATE = 77;
        private const int FUNC_TIMESTAMPADD = 78;
        private const int FUNC_TIMESTAMPDIFF = 79;
        private const int FUNC_TRUNCATE = 80;
        private const int FUNC_TO_CHAR = 81;
        private const int FUNC_TIMESTAMP = 82;
        private const int FUNC_CRYPT_KEY = 83;
        private const int FUNC_CRYPT_IV = 84;

        //
        private const int FUNC_ACOS = 101;
        private const int FUNC_ASIN = 102;
        private const int FUNC_ATAN = 103;
        private const int FUNC_ATAN2 = 104;
        private const int FUNC_COS = 105;
        private const int FUNC_COT = 106;
        private const int FUNC_DEGREES = 107;
        private const int FUNC_LOG10 = 110;
        private const int FUNC_PI = 111;
        private const int FUNC_RADIANS = 112;
        private const int FUNC_RAND = 113;
        private const int FUNC_ROUND = 114;
        private const int FUNC_SIGN = 115;
        private const int FUNC_SIN = 116;
        private const int FUNC_TAN = 117;
        private const int FUNC_BITAND = 118;
        private const int FUNC_BITOR = 119;
        private const int FUNC_BITXOR = 120;
        private const int FUNC_ROUNDMAGIC = 121;
        private const int FUNC_ASCII = 122;
        private const int FUNC_CHAR = 123;
        private const int FUNC_CONCAT = 124;
        private const int FUNC_DIFFERENCE = 125;
        private const int FUNC_HEXTORAW = 126;
        private const int FUNC_LEFT = 128;
        private const int FUNC_LOCATE = 130;
        private const int FUNC_LTRIM = 131;
        private const int FUNC_RAWTOHEX = 132;
        private const int FUNC_REPEAT = 133;
        private const int FUNC_REPLACE = 134;
        private const int FUNC_REVERSE = 135;
        private const int FUNC_RIGHT = 136;
        private const int FUNC_RTRIM = 137;
        private const int FUNC_SOUNDEX = 138;
        private const int FUNC_SPACE = 139;
        private const int FUNC_SUBSTR = 140;
        private const int FUNC_DATEADD = 141;
        private const int FUNC_DATEDIFF = 142;
        private const int FUNC_SECONDS_MIDNIGHT = 143;

        //
        static IntKeyIntValueHashMap customRegularFuncMap =
            new IntKeyIntValueHashMap();

        static FunctionCustom()
        {
            customRegularFuncMap.put(Tokens.LENGTH, FUNC_CHAR_LENGTH);
            customRegularFuncMap.put(Tokens.BITLENGTH, FUNC_BIT_LENGTH);
            customRegularFuncMap.put(Tokens.OCTETLENGTH, FUNC_OCTET_LENGTH);
            customRegularFuncMap.put(Tokens.LCASE, FUNC_FOLD_LOWER);
            customRegularFuncMap.put(Tokens.UCASE, FUNC_FOLD_UPPER);
            customRegularFuncMap.put(Tokens.LOG, FUNC_LN);

            //
            customRegularFuncMap.put(Tokens.CURDATE, FUNC_CURRENT_DATE);
            customRegularFuncMap.put(Tokens.CURTIME, FUNC_LOCALTIME);
            customRegularFuncMap.put(Tokens.SUBSTR, FUNC_SUBSTRING_CHAR);

            //
            customRegularFuncMap.put(Tokens.CRYPT_KEY, FUNC_CRYPT_KEY);

            //
            customRegularFuncMap.put(Tokens.YEAR, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.MONTH, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.DAY, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.HOUR, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.MINUTE, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.SECOND, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.DAYNAME, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.MONTHNAME, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.DAYOFMONTH, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.DAYOFWEEK, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.DAYOFYEAR, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.QUARTER, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.WEEK, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.SECONDS_MIDNIGHT, FUNC_EXTRACT);
            customRegularFuncMap.put(Tokens.LTRIM, FUNC_TRIM_CHAR);
            customRegularFuncMap.put(Tokens.RTRIM, FUNC_TRIM_CHAR);
            customRegularFuncMap.put(Tokens.LEFT, FUNC_LEFT);

            //
            customRegularFuncMap.put(Tokens.IDENTITY, FUNC_IDENTITY);
            customRegularFuncMap.put(Tokens.TIMESTAMPADD, FUNC_TIMESTAMPADD);
            customRegularFuncMap.put(Tokens.TIMESTAMPDIFF, FUNC_TIMESTAMPDIFF);
            customRegularFuncMap.put(Tokens.TRUNCATE, FUNC_TRUNCATE);
            customRegularFuncMap.put(Tokens.TO_CHAR, FUNC_TO_CHAR);
            customRegularFuncMap.put(Tokens.TIMESTAMP, FUNC_TIMESTAMP);

            //
            customRegularFuncMap.put(Tokens.LOCATE, FUNC_LOCATE);
            customRegularFuncMap.put(Tokens.INSERT, FUNC_OVERLAY_CHAR);
            customRegularFuncMap.put(Tokens.REVERSE, FUNC_REVERSE);

            //
            //
            customRegularFuncMap.put(Tokens.DATABASE, FUNC_DATABASE);
            customRegularFuncMap.put(Tokens.ISAUTOCOMMIT, FUNC_ISAUTOCOMMIT);
            customRegularFuncMap.put(Tokens.ISREADONLYSESSION,
                                     FUNC_ISREADONLYSESSION);
            customRegularFuncMap.put(Tokens.ISREADONLYDATABASE,
                                     FUNC_ISREADONLYDATABASE);
            customRegularFuncMap.put(Tokens.ISREADONLYDATABASEFILES,
                                     FUNC_ISREADONLYDATABASEFILES);

            //
            customRegularFuncMap.put(Tokens.ACOS, FUNC_ACOS);
            customRegularFuncMap.put(Tokens.ASIN, FUNC_ASIN);
            customRegularFuncMap.put(Tokens.ATAN, FUNC_ATAN);
            customRegularFuncMap.put(Tokens.ATAN2, FUNC_ATAN2);
            customRegularFuncMap.put(Tokens.COS, FUNC_COS);
            customRegularFuncMap.put(Tokens.COT, FUNC_COT);
            customRegularFuncMap.put(Tokens.DEGREES, FUNC_DEGREES);
            customRegularFuncMap.put(Tokens.LOG10, FUNC_LOG10);
            customRegularFuncMap.put(Tokens.PI, FUNC_PI);
            customRegularFuncMap.put(Tokens.RADIANS, FUNC_RADIANS);
            customRegularFuncMap.put(Tokens.RAND, FUNC_RAND);
            customRegularFuncMap.put(Tokens.ROUND, FUNC_ROUND);
            customRegularFuncMap.put(Tokens.SIGN, FUNC_SIGN);
            customRegularFuncMap.put(Tokens.SIN, FUNC_SIN);
            customRegularFuncMap.put(Tokens.TAN, FUNC_TAN);
            customRegularFuncMap.put(Tokens.BITAND, FUNC_BITAND);
            customRegularFuncMap.put(Tokens.BITOR, FUNC_BITOR);
            customRegularFuncMap.put(Tokens.BITXOR, FUNC_BITXOR);
            customRegularFuncMap.put(Tokens.ROUNDMAGIC, FUNC_ROUNDMAGIC);
            customRegularFuncMap.put(Tokens.ASCII, FUNC_ASCII);
            customRegularFuncMap.put(Tokens.CHAR, FUNC_CHAR);
            customRegularFuncMap.put(Tokens.CONCAT_WORD, FUNC_CONCAT);
            customRegularFuncMap.put(Tokens.DIFFERENCE, FUNC_DIFFERENCE);
            customRegularFuncMap.put(Tokens.HEXTORAW, FUNC_HEXTORAW);
            customRegularFuncMap.put(Tokens.RAWTOHEX, FUNC_RAWTOHEX);
            customRegularFuncMap.put(Tokens.REPEAT, FUNC_REPEAT);
            customRegularFuncMap.put(Tokens.REPLACE, FUNC_REPLACE);
            customRegularFuncMap.put(Tokens.RIGHT, FUNC_RIGHT);
            customRegularFuncMap.put(Tokens.SOUNDEX, FUNC_SOUNDEX);
            customRegularFuncMap.put(Tokens.SPACE, FUNC_SPACE);
            customRegularFuncMap.put(Tokens.DATEADD, FUNC_DATEADD);
            customRegularFuncMap.put(Tokens.DATEDIFF, FUNC_DATEDIFF);

            customValueFuncMap.put(Tokens.SYSDATE, FUNC_LOCALTIMESTAMP);
            customValueFuncMap.put(Tokens.TODAY, FUNC_CURRENT_DATE);
            customValueFuncMap.put(Tokens.NOW, FUNC_LOCALTIMESTAMP);
        }

        static IntKeyIntValueHashMap customValueFuncMap =
           new IntKeyIntValueHashMap();

        private int extractSpec;

        public static FunctionSQL newCustomFunction(String token, int tokenType)
        {

            int id = customRegularFuncMap.get(tokenType, -1);

            if (id == -1)
            {
                id = customValueFuncMap.get(tokenType, -1);
            }

            if (id == -1)
            {
                return null;
            }

            switch (tokenType)
            {

                case Tokens.LN:
                case Tokens.LCASE:
                case Tokens.UCASE:
                case Tokens.LENGTH:
                case Tokens.BITLENGTH:
                case Tokens.OCTETLENGTH:
                case Tokens.TODAY:
                case Tokens.SYSDATE:
                    return new FunctionSQL(id);

                case Tokens.NOW:
                    {
                        FunctionSQL function = new FunctionSQL(id);

                        function.parseList = optionalNoParamList;

                        return function;
                    }
                case Tokens.CURDATE:
                case Tokens.CURTIME:
                    {
                        FunctionSQL function = new FunctionSQL(id);

                        function.parseList = emptyParamList;

                        return function;
                    }
                case Tokens.SUBSTR:
                    {
                        FunctionSQL function = new FunctionSQL(id);

                        function.parseList = tripleParamList;

                        return function;
                    }
            }

            FunctionCustom _function = new FunctionCustom(id);

            if (id == FUNC_TRIM_CHAR)
            {
                switch (tokenType)
                {

                    case Tokens.LTRIM:
                        _function.extractSpec = Tokens.LEADING;
                        break;

                    case Tokens.RTRIM:
                        _function.extractSpec = Tokens.TRAILING;
                        break;
                }
            }

            if (id == FUNC_EXTRACT)
            {
                switch (tokenType)
                {

                    case Tokens.DAYNAME:
                        _function.extractSpec = Tokens.DAY_OF_WEEK;
                        break;

                    case Tokens.MONTHNAME:
                        _function.extractSpec = Tokens.MONTH_NAME;
                        break;

                    case Tokens.DAYOFMONTH:
                        _function.extractSpec = Tokens.DAY_OF_MONTH;
                        break;

                    case Tokens.DAYOFWEEK:
                        _function.extractSpec = Tokens.DAY_OF_WEEK;
                        break;

                    case Tokens.DAYOFYEAR:
                        _function.extractSpec = Tokens.DAY_OF_YEAR;
                        break;

                    default:
                        _function.extractSpec = tokenType;
                        break;
                }
            }

            if (_function.name == null)
            {
                _function.name = token;
            }

            return _function;
        }

        public static bool isRegularFunction(int tokenType)
        {
            return customRegularFuncMap.get(tokenType, -1) != -1;
        }

        public static bool isValueFunction(int tokenType)
        {
            return customValueFuncMap.get(tokenType, -1) != -1;
        }

        private FunctionCustom(int id)
            : base()
        {

            this.funcType = id;

            switch (id)
            {

                case FUNC_CONCAT:
                case FUNC_LEFT:
                    parseList = doubleParamList;
                    break;

                case FUNC_DATABASE:
                    parseList = emptyParamList;
                    break;

                case FUNC_ISAUTOCOMMIT:
                case FUNC_ISREADONLYSESSION:
                case FUNC_ISREADONLYDATABASE:
                case FUNC_ISREADONLYDATABASEFILES:
                    parseList = emptyParamList;
                    break;

                case FUNC_EXTRACT:
                    name = Tokens.T_EXTRACT;
                    parseList = singleParamList;
                    break;

                case FUNC_TRIM_CHAR:
                    name = Tokens.T_TRIM;
                    parseList = singleParamList;
                    break;

                case FUNC_OVERLAY_CHAR:
                    name = Tokens.T_OVERLAY;
                    parseList = quadParamList;
                    break;

                case FUNC_IDENTITY:
                    name = Tokens.T_IDENTITY;
                    parseList = emptyParamList;
                    break;

                case FUNC_TIMESTAMPADD:
                    name = Tokens.T_TIMESTAMPADD;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_KEYSET, 9,
                    Tokens.SQL_TSI_FRAC_SECOND, Tokens.SQL_TSI_SECOND,
                    Tokens.SQL_TSI_MINUTE, Tokens.SQL_TSI_HOUR,
                    Tokens.SQL_TSI_DAY, Tokens.SQL_TSI_WEEK,
                    Tokens.SQL_TSI_MONTH, Tokens.SQL_TSI_QUARTER,
                    Tokens.SQL_TSI_YEAR, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.COMMA, Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                    break;

                case FUNC_TIMESTAMPDIFF:
                    name = Tokens.T_TIMESTAMPDIFF;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.X_KEYSET, 9,
                    Tokens.SQL_TSI_FRAC_SECOND, Tokens.SQL_TSI_SECOND,
                    Tokens.SQL_TSI_MINUTE, Tokens.SQL_TSI_HOUR,
                    Tokens.SQL_TSI_DAY, Tokens.SQL_TSI_WEEK,
                    Tokens.SQL_TSI_MONTH, Tokens.SQL_TSI_QUARTER,
                    Tokens.SQL_TSI_YEAR, Tokens.COMMA, Tokens.QUESTION,
                    Tokens.COMMA, Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                    break;

                case FUNC_TRUNCATE:
                    parseList = doubleParamList;
                    break;

                case FUNC_TO_CHAR:
                    parseList = doubleParamList;
                    break;

                case FUNC_TIMESTAMP:
                    name = Tokens.T_TIMESTAMP;
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.X_OPTION, 2,
                    Tokens.COMMA, Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                    break;

                case FUNC_PI:
                    parseList = emptyParamList;
                    break;

                case FUNC_RAND:
                    parseList = optionalIntegerParamList;
                    break;

                case FUNC_ACOS:
                case FUNC_ASIN:
                case FUNC_ATAN:
                case FUNC_ATAN2:
                case FUNC_COS:
                case FUNC_COT:
                case FUNC_DEGREES:
                case FUNC_SIN:
                case FUNC_TAN:
                case FUNC_LOG10:
                case FUNC_RADIANS:
                case FUNC_ROUNDMAGIC:
                case FUNC_SIGN:
                case FUNC_SOUNDEX:
                case FUNC_ASCII:
                case FUNC_CHAR:
                case FUNC_HEXTORAW:
                case FUNC_RAWTOHEX:
                case FUNC_REVERSE:
                case FUNC_SPACE:
                    parseList = singleParamList;
                    break;

                case FUNC_ROUND:
                case FUNC_BITAND:
                case FUNC_BITOR:
                case FUNC_BITXOR:
                case FUNC_DIFFERENCE:
                case FUNC_REPEAT:
                case FUNC_RIGHT:
                    parseList = doubleParamList;
                    break;

                case FUNC_CRYPT_KEY:
                    parseList = doubleParamList;
                    break;

                case FUNC_LOCATE:
                    parseList = new short[] {
                    Tokens.OPENBRACKET, Tokens.QUESTION, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.X_OPTION, 2, Tokens.COMMA,
                    Tokens.QUESTION, Tokens.CLOSEBRACKET
                };
                    break;

                case FUNC_DATEADD:
                case FUNC_DATEDIFF:
                case FUNC_REPLACE:
                    parseList = tripleParamList;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "FunctionCustom");
            }
        }

        public override void setArguments(Expression[] nodes)
        {

            switch (funcType)
            {

                case FUNC_OVERLAY_CHAR:
                    {
                        Expression start = nodes[1];
                        Expression length = nodes[2];

                        nodes[1] = nodes[3];
                        nodes[2] = start;
                        nodes[3] = length;

                        break;
                    }
                case FUNC_EXTRACT:
                    {
                        Expression[] newNodes = new Expression[2];

                        newNodes[0] =
                            new ExpressionValue((extractSpec),
                                                SqlType.SQL_INTEGER);
                        newNodes[1] = nodes[0];
                        nodes = newNodes;

                        break;
                    }
                case FUNC_TRIM_CHAR:
                    {
                        Expression[] newNodes = new Expression[3];

                        newNodes[0] =
                            new ExpressionValue((extractSpec),
                                                SqlType.SQL_INTEGER);
                        newNodes[1] = new ExpressionValue(" ", SqlType.SQL_CHAR);
                        newNodes[2] = nodes[0];
                        nodes = newNodes;
                    }
                    break;
            }

            base.setArguments(nodes);
        }

        public override Expression getFunctionExpression()
        {

            switch (funcType)
            {

                case FUNC_CONCAT:
                    return new ExpressionArithmetic(OpTypes.CONCAT,
                                                    nodes[Expression.LEFT],
                                                    nodes[Expression.RIGHT]);
            }

            return base.getFunctionExpression();
        }

       public override Object getValue(Session session, Object[] data)
        {

            switch (funcType)
            {

                case FUNC_EXTRACT:
                case FUNC_TRIM_CHAR:
                case FUNC_OVERLAY_CHAR:
                    return base.getValue(session, data);

                case FUNC_DATABASE:
                    return session.getDatabase().getPath();

                case FUNC_ISAUTOCOMMIT:
                    return session.isAutoCommit() ? true
                                                  : false;

                case FUNC_ISREADONLYSESSION:
                    return session.isReadOnlyDefault() ? true
                                                       : false;

                case FUNC_ISREADONLYDATABASE:
                    return session.getDatabase().databaseReadOnly ? true
                                                                  : false;

                case FUNC_ISREADONLYDATABASEFILES:
                    return session.getDatabase().isFilesReadOnly() ? true
                                                                   : false;

                case FUNC_IDENTITY:
                    {
                        ValueType id = session.getLastIdentity();

                        if (id is long)
                        {
                            return id;
                        }
                        else
                        {
                            return (long)(int)id;
                        }
                    }
                case FUNC_TIMESTAMPADD:
                    {
                        if (data[1] == null || data[2] == null)
                        {
                            return null;
                        }

                        int part = ((int)nodes[0].valueData);
                        long units = ((long)data[1]);
                        TimestampData source = (TimestampData)data[2];
                        IntervalType t;
                        Object o;

                        switch (part)
                        {

                            case Tokens.SQL_TSI_FRAC_SECOND:
                                {
                                    long seconds = units / DTIType.limitNanoseconds;
                                    int nanos = (int)(units % DTIType.limitNanoseconds);

                                    t = SqlType.SQL_INTERVAL_SECOND_MAX_FRACTION;
                                    o = new IntervalSecondData(seconds, nanos, t);

                                    return dataType.add(source, o, t);
                                }
                            case Tokens.SQL_TSI_SECOND:
                                t = SqlType.SQL_INTERVAL_SECOND;
                                o = IntervalSecondData.newIntervalSeconds(units, t);

                                return dataType.add(source, o, t);

                            case Tokens.SQL_TSI_MINUTE:
                                t = SqlType.SQL_INTERVAL_MINUTE;
                                o = IntervalSecondData.newIntervalMinute(units, t);

                                return dataType.add(source, o, t);

                            case Tokens.SQL_TSI_HOUR:
                                t = SqlType.SQL_INTERVAL_HOUR;
                                o = IntervalSecondData.newIntervalHour(units, t);

                                return dataType.add(source, o, t);

                            case Tokens.SQL_TSI_DAY:
                                t = SqlType.SQL_INTERVAL_DAY;
                                o = IntervalSecondData.newIntervalDay(units, t);

                                return dataType.add(source, o, t);

                            case Tokens.SQL_TSI_WEEK:
                                t = SqlType.SQL_INTERVAL_DAY;
                                o = IntervalSecondData.newIntervalDay(units * 7, t);

                                return dataType.add(source, o, t);

                            case Tokens.SQL_TSI_MONTH:
                                t = SqlType.SQL_INTERVAL_MONTH;
                                o = IntervalMonthData.newIntervalMonth(units, t);

                                return dataType.add(source, o, t);

                            case Tokens.SQL_TSI_QUARTER:
                                t = SqlType.SQL_INTERVAL_MONTH;
                                o = IntervalMonthData.newIntervalMonth(units * 3, t);

                                return dataType.add(source, o, t);

                            case Tokens.SQL_TSI_YEAR:
                                t = SqlType.SQL_INTERVAL_YEAR;
                                o = IntervalMonthData.newIntervalMonth(units, t);

                                return dataType.add(source, o, t);

                            default:
                                throw Error.runtimeError(ErrorCode.U_S0500,
                                                         "FunctionCustom");
                        }
                    }
                case FUNC_TIMESTAMPDIFF:
                    {
                        if (data[1] == null || data[2] == null)
                        {
                            return null;
                        }

                        int part = ((int)nodes[0].valueData);
                        TimestampData a = (TimestampData)data[2];
                        TimestampData b = (TimestampData)data[1];

                        if (nodes[2].dataType.isDateTimeTypeWithZone())
                        {
                            a = (TimestampData)SqlType.SQL_TIMESTAMP.convertToType(
                                session, a, SqlType.SQL_TIMESTAMP_WITH_TIME_ZONE);
                        }

                        if (nodes[1].dataType.isDateTimeTypeWithZone())
                        {
                            b = (TimestampData)SqlType.SQL_TIMESTAMP.convertToType(
                                session, b, SqlType.SQL_TIMESTAMP_WITH_TIME_ZONE);
                        }

                        IntervalType t;

                        switch (part)
                        {

                            case Tokens.SQL_TSI_FRAC_SECOND:
                                t = SqlType.SQL_INTERVAL_SECOND_MAX_PRECISION;

                                IntervalSecondData interval =
                                    (IntervalSecondData)t.subtract(a, b, null);

                                return  (
                                    DTIType.limitNanoseconds * interval.getSeconds()
                                    + interval.getNanos());

                            case Tokens.SQL_TSI_SECOND:
                                t = SqlType.SQL_INTERVAL_SECOND_MAX_PRECISION;

                                return (t.convertToLong(t.subtract(a, b,
                                        null)));

                            case Tokens.SQL_TSI_MINUTE:
                                t = SqlType.SQL_INTERVAL_MINUTE_MAX_PRECISION;

                                return  (t.convertToLong(t.subtract(a, b,
                                        null)));

                            case Tokens.SQL_TSI_HOUR:
                                t = SqlType.SQL_INTERVAL_HOUR_MAX_PRECISION;

                                return  (t.convertToLong(t.subtract(a, b,
                                        null)));

                            case Tokens.SQL_TSI_DAY:
                                t = SqlType.SQL_INTERVAL_DAY_MAX_PRECISION;

                                return  (t.convertToLong(t.subtract(a, b,
                                        null)));

                            case Tokens.SQL_TSI_WEEK:
                                t = SqlType.SQL_INTERVAL_DAY_MAX_PRECISION;

                                return  (t.convertToLong(t.subtract(a, b, null))
                                                / 7);

                            case Tokens.SQL_TSI_MONTH:
                                t = SqlType.SQL_INTERVAL_MONTH_MAX_PRECISION;

                                return  (t.convertToLong(t.subtract(a, b,
                                        null)));

                            case Tokens.SQL_TSI_QUARTER:
                                t = SqlType.SQL_INTERVAL_MONTH_MAX_PRECISION;

                                return  (t.convertToLong(t.subtract(a, b, null))
                                                / 3);

                            case Tokens.SQL_TSI_YEAR:
                                t = SqlType.SQL_INTERVAL_YEAR_MAX_PRECISION;

                                return (long)(t.convertToLong(t.subtract(a, b,
                                        null)));

                            default:
                                throw Error.runtimeError(ErrorCode.U_S0500,
                                                         "FunctionCustom");
                        }
                    }
                case FUNC_SECONDS_MIDNIGHT:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }
                    }
                    goto case FUNC_TRUNCATE;
                // fall through
                case FUNC_TRUNCATE:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                        return ((NumberType)dataType).truncate(data[0],
                                ((int)data[1]));
                    }
                case FUNC_TO_CHAR:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                       
                        String format =
                            EfzDateTime.toCSharpDatePattern((String)data[1]);
                      
                        DateTime date =
                            (DateTime)((DateTimeType)nodes[0].dataType)
                                .convertSQLToCSharpGMT(session, data[0]);

                        return date.ToString(format);
                    }
                case FUNC_TIMESTAMP:
                    {
                        bool unary = nodes[1] == null;

                        if (data[0] == null)
                        {
                            return null;
                        }

                        if (unary)
                        {
                            return SqlType.SQL_TIMESTAMP.convertToType(session, data[0],
                                    nodes[0].dataType);
                        }

                        if (data[1] == null)
                        {
                            return null;
                        }

                        TimestampData date =
                            (TimestampData)SqlType.SQL_DATE.convertToType(session,
                                data[0], nodes[0].dataType);
                        TimeData time = (TimeData)SqlType.SQL_TIME.convertToType(session,
                            data[1], nodes[1].dataType);

                        return new TimestampData(date.getSeconds()
                                                 + time.getSeconds(), time.getNanos());
                    }
                case FUNC_PI:
                    return Math.PI;

                case FUNC_RAND:
                    {
                        if (nodes[0] == null)
                        {
                            return session.random();
                        }
                        else
                        {
                            long seed = ((long)data[0]);

                            return (double)(seed);
                        }
                    }
                case FUNC_ACOS:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (Math.Acos(d));
                    }
                case FUNC_ASIN:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (Math.Asin(d));
                    }
                case FUNC_ATAN:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (Math.Atan(d));
                    }
                case FUNC_COS:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (Math.Cos(d));
                    }
                case FUNC_COT:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);
                        double c = 1.0 / Math.Tan(d);

                        return (c);
                    }
                case FUNC_DEGREES:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (d); // fixup
                    }
                case FUNC_SIN:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (Math.Sin(d));
                    }
                case FUNC_TAN:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (Math.Tan(d));
                    }
                case FUNC_LOG10:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (Math.Log10(d));
                    }
                case FUNC_RADIANS:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        return (d); // fixup
                    }

                //
                case FUNC_SIGN:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        int val =
                            ((NumberType)nodes[0].dataType).compareToZero(data[0]);

                        return (val);
                    }
                case FUNC_ATAN2:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double a = NumberType.toDouble(data[0]);
                        double b = NumberType.toDouble(data[1]);

                        return Math.Atan2(a, b);
                    }
                case FUNC_ASCII:
                    {
                        String arg;

                        if (data[0] == null)
                        {
                            return null;
                        }

                        if (nodes[0].dataType.isLobType())
                        {
                            arg = ((ClobData)data[0]).getSubString(session, 0, 1);
                        }
                        else
                        {
                            arg = (String)data[0];
                        }

                        if (arg.Length == 0)
                        {
                            return null;
                        }

                        return (arg[0]);
                    }
                case FUNC_CHAR:
                    if (data[0] == null)
                    {
                        return null;
                    }

                    int argx = ((int)data[0]);

                    return argx.ToString();

                case FUNC_ROUND:
                    {
                        if (data[0] == null || data[1] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);
                        int i = ((int)data[1]);

                        d = Library.round(d, i);

                        return (d);
                    }
                case FUNC_ROUNDMAGIC:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        double d = NumberType.toDouble(data[0]);

                        d = Library.roundMagic(d);

                        return (d);
                    }
                case FUNC_SOUNDEX:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        String s = (String)data[0];

                        return Library.soundex(s);
                    }
                case FUNC_BITAND:
                case FUNC_BITOR:
                case FUNC_BITXOR:
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            if (data[0] == null)
                            {
                                return null;
                            }
                        }

                        if (nodes[0].dataType.isIntegralType())
                        {
                            long v = 0;
                            long a = ((long)data[0]);
                            long b = ((long)data[1]);

                            switch (funcType)
                            {

                                case FUNC_BITAND:
                                    v = a & b;
                                    break;

                                case FUNC_BITOR:
                                    v = a | b;
                                    break;

                                case FUNC_BITXOR:
                                    v = a ^ b;
                                    break;
                            }

                            switch (dataType.typeCode)
                            {

                                case Types.SQL_BIGINT:
                                    return (v);

                                case Types.SQL_INTEGER:
                                    return ((int)v);

                                case Types.SQL_SMALLINT:
                                    return ((int)v & 0xffff);

                                case Types.TINYINT:
                                    return ((int)v & 0xff);

                                default:
                                    throw Error.error(ErrorCode.X_42561);
                            }
                        }
                        else
                        {
                            byte[] a = ((BinaryData)data[0]).getBytes();
                            byte[] b = ((BinaryData)data[1]).getBytes();
                            byte[] v;

                            switch (funcType)
                            {

                                case FUNC_BITAND:
                                    v = BitMap.and(a, b);
                                    break;

                                case FUNC_BITOR:
                                    v = BitMap.or(a, b);
                                    break;

                                case FUNC_BITXOR:
                                    v = BitMap.xor(a, b);
                                    break;

                                default:
                                    throw Error.error(ErrorCode.X_42561);
                            }

                            return new BinaryData(v, dataType.precision);
                        }
                    }
                case FUNC_DIFFERENCE:
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            if (data[0] == null)
                            {
                                return null;
                            }
                        }

                        int v = Library.difference((String)data[0], (String)data[1]);

                        return (v);
                    }
                case FUNC_HEXTORAW:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        return dataType.convertToType(session, data[0],
                                                      nodes[0].dataType);
                    }
                case FUNC_RAWTOHEX:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        return nodes[0].dataType.convertToString(data[0]);
                    }
                case FUNC_LOCATE:
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            if (data[0] == null)
                            {
                                return null;
                            }
                        }

                        int v = Library.locate((String)data[0], (String)data[1],
                                               (int)data[2]);

                        return (v);
                    }
                case FUNC_REPEAT:
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            if (data[0] == null)
                            {
                                return null;
                            }
                        }

                        return Library.repeat(
                            (String)data[0],
                            (((int)data[1])));
                    }
                case FUNC_REPLACE:
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            if (data[0] == null)
                            {
                                return null;
                            }
                        }

                        return Library.replace((String)data[0], (String)data[1],
                                               (String)data[2]);
                    }
                case FUNC_LEFT:
                case FUNC_RIGHT:
                    {
                        for (int i = 0; i < data.Length; i++)
                        {
                            if (data[0] == null)
                            {
                                return null;
                            }
                        }

                        int count = ((int)data[1]);

                        return ((CharacterType)dataType).substring(session, data[0],
                                0, count, true, funcType == FUNC_RIGHT);
                    }
                case FUNC_SPACE:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        int count = ((int)data[0]);
                        char[] array = new char[count];

                        ArrayUtil.fillArray(array, 0, ' ');

                        return (array).ToString();
                    }
                case FUNC_REVERSE:
                    {
                        if (data[0] == null)
                        {
                            return null;
                        }

                        StringBuilder sb = new StringBuilder((String)data[0]);

                        char[] arr = sb.ToString().ToCharArray();
                        Array.Reverse(arr);

                        return new String(arr);
                    }
                case FUNC_CRYPT_KEY:
                    {
                        byte[] bytes = Crypto.getNewKey((String)data[0]);

                        return StringConverter.byteArrayToHexString(bytes);
                    }
                case FUNC_CRYPT_IV:
                    {
                        byte[] bytes = Crypto.getNewIv((String)data[0]);

                        return StringConverter.byteArrayToHexString(bytes);
                    }
                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "FunctionCustom");
            }
        }

        public override void resolveTypes(Session session, Expression parent)
        {

            for (int i = 0; i < nodes.Length; i++)
            {
                if (nodes[i] != null)
                {
                    nodes[i].resolveTypes(session, this);
                }
            }

            switch (funcType)
            {

                case FUNC_EXTRACT:
                case FUNC_TRIM_CHAR:
                case FUNC_OVERLAY_CHAR:
                    base.resolveTypes(session, parent);

                    return;

                case FUNC_DATABASE:
                    dataType = SqlType.SQL_VARCHAR_DEFAULT;

                    return;

                case FUNC_ISAUTOCOMMIT:
                case FUNC_ISREADONLYSESSION:
                case FUNC_ISREADONLYDATABASE:
                case FUNC_ISREADONLYDATABASEFILES:
                    dataType = SqlType.SQL_BOOLEAN;

                    return;

                case FUNC_IDENTITY:
                    dataType = SqlType.SQL_BIGINT;

                    return;

                case FUNC_DATEADD:
                    {
                        int part;

                        if (!nodes[0].dataType.isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        if ("yy".Equals((String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_YEAR;
                        }
                        else if ("mm".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_MONTH;
                        }
                        else if ("dd".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_DAY;
                        }
                        else if ("hh".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_HOUR;
                        }
                        else if ("mi".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_MINUTE;
                        }
                        else if ("ss".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_SECOND;
                        }
                        else if ("ms".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_FRAC_SECOND;
                        }
                        else
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        nodes[0].valueData = (part);
                        funcType = FUNC_TIMESTAMPADD;
                    }
                    goto case FUNC_TIMESTAMPADD;
                // fall through
                case FUNC_TIMESTAMPADD:
                    if (nodes[1].dataType == null)
                    {
                        nodes[1].dataType = SqlType.SQL_BIGINT;
                    }

                    if (nodes[2].dataType == null)
                    {
                        nodes[2].dataType = SqlType.SQL_TIMESTAMP;
                    }

                    if (!nodes[1].dataType.isIntegralType())
                    {
                        throw Error.error(ErrorCode.X_42561);
                    }

                    if (nodes[2].dataType.typeCode != Types.SQL_DATE
                            && nodes[2].dataType.typeCode != Types.SQL_TIMESTAMP
                            && nodes[2].dataType.typeCode
                               != Types.SQL_TIMESTAMP_WITH_TIME_ZONE)
                    {
                        throw Error.error(ErrorCode.X_42561);
                    }

                    dataType = nodes[2].dataType;

                    return;

                case FUNC_DATEDIFF:
                    {
                        int part;

                        if (!nodes[0].dataType.isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        if ("yy".Equals((String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_YEAR;
                        }
                        else if ("mm".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_MONTH;
                        }
                        else if ("dd".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_DAY;
                        }
                        else if ("hh".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_HOUR;
                        }
                        else if ("mi".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_MINUTE;
                        }
                        else if ("ss".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_SECOND;
                        }
                        else if ("ms".Equals(
                              (String)nodes[0].valueData, StringComparison.OrdinalIgnoreCase))
                        {
                            part = Tokens.SQL_TSI_FRAC_SECOND;
                        }
                        else
                        {
                            throw Error.error(ErrorCode.X_22511,
                                              (String)nodes[0].valueData);
                        }

                        nodes[0].valueData = (part);
                        funcType = FUNC_TIMESTAMPDIFF;
                    }
                    goto case FUNC_TIMESTAMPDIFF;
                // fall through
                case FUNC_TIMESTAMPDIFF:
                    {
                        if (nodes[1].dataType == null)
                        {
                            nodes[1].dataType = nodes[2].dataType;
                        }

                        if (nodes[2].dataType == null)
                        {
                            nodes[2].dataType = nodes[1].dataType;
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[1].dataType = SqlType.SQL_TIMESTAMP;
                            nodes[2].dataType = SqlType.SQL_TIMESTAMP;
                        }

                        switch (nodes[1].dataType.typeCode)
                        {

                            case Types.SQL_DATE:
                                if (nodes[2].dataType.typeCode != Types.SQL_DATE)
                                {
                                    throw Error.error(ErrorCode.X_42565);
                                }

                                switch (((int)nodes[0].valueData))
                                {

                                    case Tokens.SQL_TSI_DAY:
                                    case Tokens.SQL_TSI_WEEK:
                                    case Tokens.SQL_TSI_MONTH:
                                    case Tokens.SQL_TSI_QUARTER:
                                    case Tokens.SQL_TSI_YEAR:
                                        break;

                                    default:
                                        throw Error.error(ErrorCode.X_42565);
                                }
                                break;

                            case Types.SQL_TIMESTAMP:
                            case Types.SQL_TIMESTAMP_WITH_TIME_ZONE:
                                if (nodes[2].dataType.typeCode != Types.SQL_TIMESTAMP
                                        && nodes[2].dataType.typeCode
                                           != Types.SQL_TIMESTAMP_WITH_TIME_ZONE)
                                {
                                    throw Error.error(ErrorCode.X_42565);
                                }
                                break;

                            default:
                                throw Error.error(ErrorCode.X_42565);
                        }

                        dataType = SqlType.SQL_BIGINT;

                        return;
                    }
                case FUNC_TRUNCATE:
                    {
                        if (nodes[0].dataType == null)
                        {
                            throw Error.error(ErrorCode.X_42567);
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[1].dataType = SqlType.SQL_INTEGER;
                        }
                        else if (!nodes[1].dataType.isIntegralType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        if (!nodes[0].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        dataType = nodes[0].dataType;

                        return;
                    }
                case FUNC_TO_CHAR:
                    {
                        if (nodes[0].dataType == null)
                        {
                            throw Error.error(ErrorCode.X_42567);
                        }

                        if (nodes[1].dataType == null
                                || !nodes[1].dataType.isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_42567);
                        }

                        if (!nodes[0].dataType.isExactNumberType()
                                && !nodes[0].dataType.isDateTimeType())
                        {
                            throw Error.error(ErrorCode.X_42565);
                        }

                        // fixed maximum as format is a variable
                        dataType = CharacterType.getCharacterType(Types.SQL_VARCHAR,
                                40);

                        if (nodes[1].opType == OpTypes.VALUE)
                        {
                            nodes[1].setAsConstantValue(session);
                        }

                        return;
                    }
                case FUNC_TIMESTAMP:
                    {
                        SqlType argType = nodes[0].dataType;

                        if (nodes[1] == null)
                        {
                            if (argType == null)
                            {
                                argType = nodes[0].dataType = SqlType.SQL_VARCHAR_DEFAULT;
                            }

                            if (argType.isCharacterType()
                                    || argType.typeCode == Types.SQL_TIMESTAMP
                                    || argType.typeCode
                                       == Types.SQL_TIMESTAMP_WITH_TIME_ZONE) { }
                            else
                            {
                                throw Error.error(ErrorCode.X_42561);
                            }
                        }
                        else
                        {
                            if (argType == null)
                            {
                                if (nodes[1].dataType == null)
                                {
                                    argType = nodes[0].dataType = nodes[1].dataType =
                                        SqlType.SQL_VARCHAR_DEFAULT;
                                }
                                else
                                {
                                    if (nodes[1].dataType.isCharacterType())
                                    {
                                        argType = nodes[0].dataType =
                                            SqlType.SQL_VARCHAR_DEFAULT;
                                    }
                                    else
                                    {
                                        argType = nodes[0].dataType = SqlType.SQL_DATE;
                                    }
                                }
                            }

                            if (nodes[1].dataType == null)
                            {
                                if (argType.isCharacterType())
                                {
                                    nodes[1].dataType = SqlType.SQL_VARCHAR_DEFAULT;
                                }
                                else if (argType.typeCode == Types.SQL_DATE)
                                {
                                    nodes[1].dataType = SqlType.SQL_TIME;
                                }
                            }

                            if ((argType.typeCode == Types.SQL_DATE && nodes[1]
                                    .dataType.typeCode == Types.SQL_TIME) || argType
                                        .isCharacterType() && nodes[1].dataType
                                        .isCharacterType()) { }
                            else
                            {
                                throw Error.error(ErrorCode.X_42561);
                            }
                        }

                        dataType = SqlType.SQL_TIMESTAMP;

                        return;
                    }
                case FUNC_PI:
                    dataType = SqlType.SQL_DOUBLE;
                    break;

                case FUNC_RAND:
                    {
                        if (nodes[0] != null)
                        {
                            if (nodes[0].dataType == null)
                            {
                                nodes[0].dataType = SqlType.SQL_BIGINT;
                            }
                            else if (!nodes[0].dataType.isExactNumberType())
                            {
                                throw Error.error(ErrorCode.X_42565);
                            }
                        }

                        dataType = SqlType.SQL_DOUBLE;

                        break;
                    }
                case FUNC_ROUND:
                    if (nodes[1].dataType == null)
                    {
                        nodes[1].dataType = SqlType.SQL_INTEGER;
                    }

                    if (!nodes[1].dataType.isExactNumberType())
                    {
                        throw Error.error(ErrorCode.X_42561);
                    }
                    goto case FUNC_ROUNDMAGIC;
                // fall through
                case FUNC_ACOS:
                case FUNC_ASIN:
                case FUNC_ATAN:
                case FUNC_COS:
                case FUNC_COT:
                case FUNC_DEGREES:
                case FUNC_SIN:
                case FUNC_TAN:
                case FUNC_LOG10:
                case FUNC_RADIANS:
                case FUNC_ROUNDMAGIC:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_DOUBLE;
                        }

                        if (!nodes[0].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = SqlType.SQL_DOUBLE;

                        break;
                    }
                case FUNC_SIGN:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_DOUBLE;
                        }

                        if (!nodes[0].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = SqlType.SQL_INTEGER;

                        break;
                    }
                case FUNC_ATAN2:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_DOUBLE;
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[1].dataType = SqlType.SQL_DOUBLE;
                        }

                        if (!nodes[0].dataType.isNumberType()
                                || !nodes[1].dataType.isNumberType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = SqlType.SQL_DOUBLE;

                        break;
                    }
                case FUNC_SOUNDEX:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_VARCHAR;
                        }

                        if (!nodes[0].dataType.isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = SqlType.getType(Types.SQL_VARCHAR, 0, 4, 0);

                        break;
                    }
                case FUNC_BITAND:
                case FUNC_BITOR:
                case FUNC_BITXOR:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = nodes[1].dataType;
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[1].dataType = nodes[0].dataType;
                        }

                        for (int i = 0; i < nodes.Length; i++)
                        {
                            if (nodes[i].dataType == null)
                            {
                                nodes[i].dataType = SqlType.SQL_INTEGER;
                            }
                        }

                        dataType =
                            nodes[0].dataType.getAggregateType(nodes[1].dataType);

                        switch (dataType.typeCode)
                        {

                            case Types.SQL_BIGINT:
                            case Types.SQL_INTEGER:
                            case Types.SQL_SMALLINT:
                            case Types.TINYINT:
                                break;

                            case Types.SQL_BIT:
                            case Types.SQL_BIT_VARYING:
                                break;

                            default:
                                throw Error.error(ErrorCode.X_42561);
                        }

                        break;
                    }
                case FUNC_ASCII:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_VARCHAR;
                        }

                        if (!nodes[0].dataType.isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = SqlType.SQL_INTEGER;

                        break;
                    }
                case FUNC_CHAR:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_INTEGER;
                        }

                        if (!nodes[0].dataType.isExactNumberType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = SqlType.getType(Types.SQL_VARCHAR, 0, 1, 0);

                        break;
                    }
                case FUNC_DIFFERENCE:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_VARCHAR;
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[1].dataType = SqlType.SQL_VARCHAR;
                        }

                        dataType = SqlType.SQL_INTEGER;

                        break;
                    }
                case FUNC_HEXTORAW:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_VARCHAR;
                        }

                        if (!nodes[0].dataType.isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = nodes[0].dataType.precision == 0
                                   ? SqlType.SQL_VARBINARY_DEFAULT
                                   : SqlType.getType(Types.SQL_VARBINARY, 0,
                                                  nodes[0].dataType.precision / 2, 0);

                        break;
                    }
                case FUNC_RAWTOHEX:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_VARBINARY;
                        }

                        if (!nodes[0].dataType.isBinaryType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = nodes[0].dataType.precision == 0
                                   ? SqlType.SQL_VARCHAR_DEFAULT
                                   : SqlType.getType(Types.SQL_VARCHAR, 0,
                                                  nodes[0].dataType.precision * 2, 0);

                        break;
                    }
                case FUNC_LOCATE:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_VARCHAR;
                        }

                        if (nodes[1].dataType == null)
                        {
                            nodes[1].dataType = SqlType.SQL_VARCHAR;
                        }

                        if (nodes[2] == null)
                        {
                            nodes[2] = new ExpressionValue(0,
                                                           SqlType.SQL_INTEGER);
                        }

                        if (nodes[2].dataType == null)
                        {
                            nodes[2].dataType = SqlType.SQL_INTEGER;
                        }

                        bool isChar = nodes[0].dataType.isCharacterType()
                                         && nodes[1].dataType.isCharacterType();

                        if (!isChar || !nodes[2].dataType.isExactNumberType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = SqlType.SQL_INTEGER; ;

                        break;
                    }
                case FUNC_REPEAT:
                    {
                        if (nodes[0].dataType == null)
                        {
                            nodes[0].dataType = SqlType.SQL_VARCHAR;
                        }

                        bool isChar = nodes[0].dataType.isCharacterType();

                        if (!isChar && !nodes[0].dataType.isBinaryType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }

                        dataType = isChar ? (SqlType)SqlType.SQL_VARCHAR_DEFAULT
                                          : (SqlType)SqlType.SQL_VARBINARY_DEFAULT;

                        break;
                    }
                case FUNC_REPLACE:
                    {
                        for (int i = 0; i < nodes.Length; i++)
                        {
                            if (nodes[i].dataType == null)
                            {
                                nodes[i].dataType = SqlType.SQL_VARCHAR;
                            }
                            else if (!nodes[i].dataType.isCharacterType())
                            {
                                throw Error.error(ErrorCode.X_42561);
                            }
                        }

                        dataType = SqlType.SQL_VARCHAR_DEFAULT;

                        break;
                    }
                case FUNC_LEFT:
                case FUNC_RIGHT:
                    if (nodes[0].dataType == null)
                    {
                        nodes[0].dataType = SqlType.SQL_VARCHAR;
                    }

                    if (!nodes[0].dataType.isCharacterType())
                    {
                        throw Error.error(ErrorCode.X_42561);
                    }

                    if (nodes[1].dataType == null)
                    {
                        nodes[1].dataType = SqlType.SQL_INTEGER;
                    }

                    if (!nodes[1].dataType.isExactNumberType())
                    {
                        throw Error.error(ErrorCode.X_42561);
                    }

                    dataType = nodes[0].dataType.precision == 0
                               ? SqlType.SQL_VARCHAR_DEFAULT
                               : SqlType.getType(Types.SQL_VARCHAR, 0,
                                              nodes[0].dataType.precision, 0);
                    break;

                case FUNC_SPACE:
                    if (nodes[0].dataType == null)
                    {
                        nodes[0].dataType = SqlType.SQL_INTEGER;
                    }

                    if (!nodes[0].dataType.isIntegralType())
                    {
                        throw Error.error(ErrorCode.X_42561);
                    }

                    dataType = SqlType.SQL_VARCHAR_DEFAULT;
                    break;

                case FUNC_REVERSE:
                    if (nodes[0].dataType == null)
                    {
                        nodes[0].dataType = SqlType.SQL_VARCHAR_DEFAULT;
                    }

                    dataType = nodes[0].dataType;

                    if (!dataType.isCharacterType() || dataType.isLobType())
                    {
                        throw Error.error(ErrorCode.X_42561);
                    }
                    break;

                case FUNC_CRYPT_KEY:
                    for (int i = 0; i < nodes.Length; i++)
                    {
                        if (nodes[i].dataType == null)
                        {
                            nodes[i].dataType = SqlType.SQL_VARCHAR;
                        }
                        else if (!nodes[i].dataType.isCharacterType())
                        {
                            throw Error.error(ErrorCode.X_42561);
                        }
                    }

                    dataType = SqlType.SQL_VARCHAR_DEFAULT;
                    break;

                default:
                    throw Error.runtimeError(ErrorCode.U_S0500, "FunctionCustom");
            }
        }

        public override String getSQL()
        {

            switch (funcType)
            {

                case FUNC_EXTRACT:
                case FUNC_TRIM_CHAR:
                case FUNC_OVERLAY_CHAR:
                    return base.getSQL();

                case FUNC_DATABASE:
                case FUNC_ISAUTOCOMMIT:
                case FUNC_ISREADONLYSESSION:
                case FUNC_ISREADONLYDATABASE:
                case FUNC_ISREADONLYDATABASEFILES:
                    return new StringBuilder(name).Append(
                        Tokens.T_OPENBRACKET).Append(
                        Tokens.T_CLOSEBRACKET).ToString();

                case FUNC_IDENTITY:
                    return new StringBuilder(Tokens.T_IDENTITY).Append(
                        Tokens.T_OPENBRACKET).Append(
                        Tokens.T_CLOSEBRACKET).ToString();

                case FUNC_TIMESTAMPADD:
                    return new StringBuilder(Tokens.T_TIMESTAMPADD).Append(
                        Tokens.T_OPENBRACKET).Append(nodes[0].getSQL())       //
                        .Append(Tokens.T_COMMA).Append(nodes[1].getSQL())     //
                        .Append(Tokens.T_COMMA).Append(nodes[2].getSQL())     //
                        .Append(Tokens.T_CLOSEBRACKET).ToString();

                case FUNC_TIMESTAMPDIFF:
                    return new StringBuilder(Tokens.T_TIMESTAMPDIFF).Append(
                        Tokens.T_OPENBRACKET).Append(nodes[0].getSQL())       //
                        .Append(Tokens.T_COMMA).Append(nodes[1].getSQL())     //
                        .Append(Tokens.T_COMMA).Append(nodes[2].getSQL())     //
                        .Append(Tokens.T_CLOSEBRACKET).ToString();

                case FUNC_TRUNCATE:
                    {
                        return new StringBuilder(Tokens.T_TRUNCATE).Append('(')    //
                            .Append(nodes[0].getSQL()).Append(Tokens.T_COMMA)     //
                            .Append(nodes[1].getSQL()).Append(')').ToString();
                    }
                case FUNC_TO_CHAR:
                    {
                        return new StringBuilder(Tokens.T_TO_CHAR).Append('(')     //
                            .Append(nodes[0].getSQL()).Append(Tokens.T_COMMA)     //
                            .Append(nodes[1].getSQL()).Append(')').ToString();
                    }
                case FUNC_PI:
                case FUNC_RAND:
                    {
                        return new StringBuilder(name).Append('(').Append(
                            ')').ToString();
                    }
                case FUNC_ACOS:
                case FUNC_ASIN:
                case FUNC_ATAN:
                case FUNC_ATAN2:
                case FUNC_COS:
                case FUNC_COT:
                case FUNC_DEGREES:
                case FUNC_SIN:
                case FUNC_TAN:
                case FUNC_LOG10:
                case FUNC_RADIANS:
                case FUNC_ROUNDMAGIC:
                case FUNC_SIGN:
                    {
                        return new StringBuilder(name).Append('(')                 //
                            .Append(nodes[0].getSQL()).Append(')').ToString();
                    }
                case FUNC_ROUND:
                    {
                        return new StringBuilder(Tokens.ROUND).Append('(')         //
                            .Append(nodes[0].getSQL()).Append(Tokens.T_COMMA)     //
                            .Append(nodes[1].getSQL()).Append(')').ToString();
                    }
                case FUNC_CRYPT_KEY:
                    {
                        return new StringBuilder(Tokens.CRYPT_KEY).Append('(')     //
                            .Append(nodes[0].getSQL()).Append(Tokens.T_COMMA)     //
                            .Append(nodes[1].getSQL()).Append(')').ToString();
                    }
                default:
                    return base.getSQL();
            }
        }
    }
}
